﻿using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Windows.Threading;
using System.Windows;
using System.Threading;
using System.IO;
using System.Windows.Media;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Threading.Tasks;
using System.Media;
using System.ComponentModel;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Ioc;
using JariZ;
using JmShared;
using JiveMessenger.Network;
using JiveMessenger.CustomControls;
using JiveMessenger.ViewModels;
using JiveMessenger.NotifyWindow;
using JiveMessenger.Models;
using JiveMessenger.AvalonEdit;
using JiveMessenger.Utilities;
using System.Collections.ObjectModel;
using System.Text;
using NLog;
using Newtonsoft.Json;
using JiveMessenger.Misc;
using System.Diagnostics;
using System.Windows.Input;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using WinampLib;


namespace JiveMessenger.User
{
    public class Client : IClient, INotifyPropertyChanged
    {
        private Dictionary<string, List<FileSlice>> _receivedSlices;
        private string _lastConnectedServer;
        private readonly object MessageQueueLockObject = new object();
        private Dictionary<int, Dictionary<int, ChatMessage>> _queuedMessages;
        private Dictionary<int, int> _processedMessageCount;
        private Dictionary<int, List<ChatMessage>> _addedBatchMessages;
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private bool _isPlayingNotificationSound;
        private string _loggedInUserID;
        private Thread _initSpotifyThread;
        private bool _firstConnect = true;
        private bool _receivedClientList = false;
        private System.Timers.Timer _statusTimer;
        private System.Timers.Timer _receiptTimer;
        private string _deviceID;
        private string DeviceID
        {
            get
            {
                if (string.IsNullOrEmpty(_deviceID))
                    _deviceID = StaticMethods.GetUniqueID();
                return _deviceID;
            }
        }

        public SpotifyAPI SpotifyApi { get; set; }
        public string LastConnectedUserName { get; set; }
        public PcUserSettings UserSettings { get; set; }
        public bool StopReconnectRetrying { get; set; }
        public bool IsUserConnecting { get; set; }
        public bool IsRetrying { get; set; }
        public int MessageID { get; set; }

        private Dictionary<string, IEmoticon> SymbolEmoticons;


        private bool _isLoggedIn;
        public bool IsLoggedIn
        {
            get { return _isLoggedIn; }
            set
            {
                if (_isLoggedIn != value)
                {
                    _isLoggedIn = value;
                    RaisePropertyChanged("IsLoggedIn");
                }
            }
        }

        private bool _isAfk;
        public bool IsAfk
        {
            get { return _isAfk; }
            set
            {
                if (_isAfk != value)
                {
                    _isAfk = value;
                    RaisePropertyChanged("IsAfk");
                }
            }
        }

        private bool _isBusy;
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy != value)
                {
                    _isBusy = value;
                    RaisePropertyChanged("IsBusy");
                }
            }
        }
        private ObservableCollection<JiveDefaultEmoticonModel> _defaultEmoticons;
        public ObservableCollection<JiveDefaultEmoticonModel> DefaultEmoticons
        {
            get { return _defaultEmoticons; }
            set
            {
                if (_defaultEmoticons != value)
                {
                    _defaultEmoticons = value;
                }
            }
        }


        private Dictionary<int, KeyValuePair<DateTime, ChatMessage>> _sendtMessages;
        private Dictionary<int, KeyValuePair<DateTime, ChatMessage>> SentMessages
        {

            get
            {
                if (_sendtMessages == null)
                    _sendtMessages = new Dictionary<int, KeyValuePair<DateTime, ChatMessage>>();

                return _sendtMessages;
            }
            set { _sendtMessages = value; }
        }


        private ConcurrentDictionary<string, ViewModelChatWindow> _openWindowViewModels;

        public ConcurrentDictionary<string, ViewModelChatWindow> OpenWindowViewModels
        {
            get
            {
                if (_openWindowViewModels == null)
                    _openWindowViewModels = new ConcurrentDictionary<string, ViewModelChatWindow>();
                return _openWindowViewModels;
            }
            set { _openWindowViewModels = value; }
        }

        /// <summary>
        /// Conversations that are not open, key is GroupID
        /// </summary>
        private ConcurrentDictionary<string, ViewModelChat> _closedConversations;

        public ConcurrentDictionary<string, ViewModelChat> ClosedConversations
        {
            get
            {
                if (_closedConversations == null)
                    _closedConversations = new ConcurrentDictionary<string, ViewModelChat>();
                return _closedConversations;
            }
            set { _closedConversations = value; }
        }

        private NetworkConnection _connection;
        public NetworkConnection Connection
        {
            get
            {
                if (_connection == null)
                {
                    _connection = new NetworkConnection(DeviceID, Assembly.GetExecutingAssembly().GetName().Version.ToString());
                    AttachEvents(Connection);
                }
                return _connection;
            }
            set
            {
                if (_connection != null)
                    DetachEvents(_connection);
                _connection = value;
                if (_connection != null)
                {
                    AttachEvents(_connection);
                }
            }
        }

        public UserInfo CurrentUserInfo
        {
            get
            {
                UserInfo user;
                if (ContactClients.TryGetValue(_loggedInUserID, out user))
                    return user;
                else
                    return null;

            }
        }

        private ConcurrentDictionary<string, UserInfo> _contactClients;
        public ConcurrentDictionary<string, UserInfo> ContactClients
        {
            get
            {
                if (_contactClients == null)
                    _contactClients = new ConcurrentDictionary<string, UserInfo>();
                return _contactClients;
            }
            set { _contactClients = value; }
        }

        private List<string> _friendUsernames;
        public List<string> FriendUsernames
        {
            get
            {
                if (_friendUsernames == null)
                    _friendUsernames = new List<string>();
                return _friendUsernames;
            }
            set { _friendUsernames = value; }
        }

        public PrivateMessageBehavior PrivateMessageBehavior
        {
            get
            {
                return (PrivateMessageBehavior)Enum.Parse(typeof(PrivateMessageBehavior), Properties.Settings.Default.SettingPrivateMessageLocation);
            }
        }

        public void ClearConnectedClients()
        {
            ContactClients.Clear();
            _loggedInUserID = string.Empty;
        }

        public Client()
        {
            this.DefaultEmoticons = CreateDefaultEmoticonList();
            this.SymbolEmoticons = CreateSymbolList();
            NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;
            NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
        }

        void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            if (!e.IsAvailable && IsLoggedIn)
            {
                if (Connection != null)
                    Connection.Disconnect();
            }
            else if (e.IsAvailable && !IsLoggedIn)
            {
                if (Connection != null)
                    Connection.Login(Properties.Settings.Default.Server, Properties.Settings.Default.Username, Properties.Settings.Default.Password, ProgramSettings.Instance.ServerPort);
            }
        }

        void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
        {
            if (!IsLoggedIn)
                return;
            // NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();
            //foreach (NetworkInterface n in adapters)
            //{
            //  Console.WriteLine("   {0} is {1}", n.Name, n.OperationalStatus);
            if (Connection != null)
                Connection.Disconnect();
            //}
        }
        private Dictionary<string, IEmoticon> CreateSymbolList()
        {
            return new Dictionary<string, IEmoticon> { 
                { "<line>", new JiveSymbolEmoticonModel(){ EmoticonPath="/Resources/Symbols/line.png", EmoticonShortcut="<line>" } },
                { "<spacer>", new JiveSymbolEmoticonModel(){ EmoticonPath="/Resources/Symbols/spacer.png", EmoticonShortcut="<spacer>" } },
                { "<s_music>", new JiveSymbolEmoticonModel(){ EmoticonPath="/Resources/Symbols/music_left.png", EmoticonShortcut="<s_music>" } },
                { "</s_music>", new JiveSymbolEmoticonModel(){ EmoticonPath="/Resources/Symbols/music_right.png", EmoticonShortcut="</s_music>" } },
                { "<w_music>", new JiveSymbolEmoticonModel(){ EmoticonPath="/Resources/Symbols/music_left.png", EmoticonShortcut="<w_music>" } },
                { "</w_music>",new JiveSymbolEmoticonModel(){ EmoticonPath="/Resources/Symbols/music_right.png" , EmoticonShortcut="</w_music>" } }};

        }
        private ObservableCollection<JiveDefaultEmoticonModel> CreateDefaultEmoticonList()
        {
            Dictionary<string[], string> defaultEmoticons = ProgramSettings.Instance.DefaultEmoticons;
            ObservableCollection<JiveDefaultEmoticonModel> list = new ObservableCollection<JiveDefaultEmoticonModel>();
            foreach (KeyValuePair<string[], string> kvp in defaultEmoticons)
            {
                list.Add(new JiveDefaultEmoticonModel() { EmoticonShortcut = kvp.Key, EmoticonPath = "/Resources/Emoticons/" + kvp.Value });
            }
            return list;
        }
        private void AttachEvents(NetworkConnection connection)
        {
            connection.ChatMessageReceived += Connection_ChatMessageReceived;
            connection.ContactEmoticonReceived += Connection_ContactEmoticonReceived;
            connection.ChatMessageSendt += Connection_ChatMessageSendt;
            connection.ClientListReceived += Connection_ClientListReceived;
            connection.ChatReceiptReceived += Connection_ChatReceiptReceived;
            connection.OnDisconnected += Connection_OnDisconnected;
            connection.UserSettingsReceived += Connection_UserSettingsReceived;
            connection.ServerInfoMessageReceived += Connection_ServerInfoMessageReceived;
            connection.DeviceStatusReceived += Connection_DeviceStatusReceived;
            connection.NotificationListReceived += connection_NotificationListReceived;
            connection.LoginResponseReceived += connection_LoginResponseReceived;
            connection.AvatarReceived += connection_AvatarReceived;
            connection.EmoticonRequestReceived += connection_EmoticonRequestReceived;
            connection.AvatarRequestReceived += connection_AvatarRequestReceived;
            connection.MessageQueueReceived += connection_MessageQueueReceived;
            connection.FileSliceReceived += connection_FileSliceReceived;
        }

        private void DetachEvents(NetworkConnection connection)
        {
            connection.ChatMessageReceived -= Connection_ChatMessageReceived;
            connection.ContactEmoticonReceived -= Connection_ContactEmoticonReceived;
            connection.ChatMessageSendt -= Connection_ChatMessageSendt;
            connection.ClientListReceived -= Connection_ClientListReceived;
            connection.ChatReceiptReceived -= Connection_ChatReceiptReceived;
            connection.OnDisconnected -= Connection_OnDisconnected;
            connection.UserSettingsReceived -= Connection_UserSettingsReceived;
            connection.ServerInfoMessageReceived -= Connection_ServerInfoMessageReceived;
            connection.DeviceStatusReceived -= Connection_DeviceStatusReceived;
            connection.NotificationListReceived -= connection_NotificationListReceived;
            connection.LoginResponseReceived -= connection_LoginResponseReceived;
            connection.AvatarReceived -= connection_AvatarReceived;
            connection.EmoticonRequestReceived -= connection_EmoticonRequestReceived;
            connection.AvatarRequestReceived -= connection_AvatarRequestReceived;
            connection.MessageQueueReceived -= connection_MessageQueueReceived;
            connection.FileSliceReceived -= connection_FileSliceReceived;
        }
        void connection_FileSliceReceived(object sender, FileSliceEventArgs e)
        {
            FileSlice slice = e.FileSlice;
            if (_receivedSlices == null)
                _receivedSlices = new Dictionary<string, List<FileSlice>>();
            if (!_receivedSlices.ContainsKey(slice.TransferID))
                _receivedSlices[slice.TransferID] = new List<FileSlice>();

            _receivedSlices[slice.TransferID].Add(slice);

            if (_receivedSlices[slice.TransferID].Count == slice.SliceCount)
            {
                string data = "";
                foreach (FileSlice s in _receivedSlices[slice.TransferID].OrderBy(x => x.SliceNumber))
                {
                    data += s.Data;
                }
                switch (slice.TransferType)
                {
                    case JmShared.FileTransferType.CustomEmoticon:
                        ContactEmoticonTransfer ce = JsonConvert.DeserializeObject<ContactEmoticonTransfer>(data);
                        ReceiveEmoticon(ce);
                        break;
                    case FileTransferType.Avatar:
                        AvatarTransfer avatar = JsonConvert.DeserializeObject<AvatarTransfer>(data);

                        if (avatar.AvatarByteArray == null)
                            return;
                        SaveAvatarToDisk(ContactClients[avatar.SenderID], avatar.AvatarByteArray, avatar.Hash, avatar.Extension);
                        break;
                }
                _receivedSlices.Remove(slice.TransferID);
            }
        }

        private void connection_MessageQueueReceived(object sender, MessageQueueEventArgs e)
        {
            MessageQueue queue = e.MessageQueue;
            Messenger.Default.Send(new NotificationMessage(this, "LockMainPagePivot"));
            Messenger.Default.Send(new NotificationMessage(this, queue.ChatID, "BeginMessageQueue"));

            int i = 1;
            MessageType type = queue.Messages.FirstOrDefault().MessageType;
            App.Current.Dispatcher.Invoke(() =>
                {
                    foreach (ChatMessage msg in queue.Messages)
                    {
                        msg.MessageRelayTime = TimeZoneInfo.ConvertTime(msg.MessageRelayTime.Value, TimeZoneInfo.Local);
                        if (type == MessageType.GroupMessage)
                            DisplayGroupMessage(msg);
                        else if (type == MessageType.PrivateMessage)
                            DisplayPrivateMessage(msg, false);

                        Messenger.Default.Send(new NotificationMessage<MessageQueueInfo>(this, queue.ChatID, new MessageQueueInfo(i, queue.MessageCount), "UpdateMessageQueueInfo"));
                        i++;
                        //await Task.Delay(20);
                    }
                });
            Messenger.Default.Send(new NotificationMessage(this, queue.ChatID, "EndMessageQueue"));
            Messenger.Default.Send(new NotificationMessage(this, "UnlockMainPagePivot"));
            //await Task.Delay(200);
            //Messenger.Default.Send(new NotificationMessage(this, queue.ChatID, "ScrollToEnd"));
        }
        private void connection_AvatarRequestReceived(object sender, AvatarRequestEventArgs e)
        {
            AvatarRequest request = e.AvatarRequest;
            string path = StaticMethods.CombinePath(ProgramSettings.Instance.AvatarFolder, this.CurrentUserInfo.UserName, request.Hash + request.Extension);
            try
            {
                if (!string.IsNullOrEmpty(path))
                {
                    byte[] imgArray = StaticMethods.LoadImageData(path);

                    if (imgArray == null || imgArray.Length == 0)
                        return;


                    var avatar = new AvatarTransfer
                    {
                        AvatarByteArray = imgArray,
                        Extension = Path.GetExtension(path),
                        SenderID = CurrentUserInfo.UserID
                    };
                    if (imgArray.Length < 50000)
                    {
                        Connection.SendCommand(avatar, ClientCommands.Avatar);
                    }
                    else //We need to send in chunks
                    {
                        string data = JsonConvert.SerializeObject(avatar);
                        List<string> chunks = SharedStaticMethods.SplitLongMessage(data, 50000); //We send in chunks of 50KB
                        int i = 1;
                        string id = new ShortGuid(Guid.NewGuid());
                        foreach (string chunk in chunks)
                        {
                            FileSlice slice = new FileSlice() { Data = chunk, SliceCount = chunks.Count, SliceNumber = i, TransferID = id, TransferType = FileTransferType.Avatar };
                            Connection.SendCommand(slice, ClientCommands.FileTransfer);
                            i++;
                        }
                    }
                }
                else
                    Logger.Error("Avatar request failed, avatar not found for key :" + request.Hash);
            }
            catch (Exception ex)
            {
                Logger.ErrorException("Error sending avatar to server", ex);
            }
        }

        void connection_EmoticonRequestReceived(object sender, EmoticonRequestEventArgs e)
        {
            EmoticonRequest request = e.EmoticonRequest;
            string path = this.GetEmoticons(this.CurrentUserInfo.UserID, false, true, false)[request.KeyWord].EmoticonPath;
            try
            {
                if (!string.IsNullOrEmpty(path))
                {
                    byte[] imgArray = StaticMethods.LoadImageData(path);

                    if (imgArray == null || imgArray.Length == 0)
                        return;


                    var ce = new ContactEmoticonTransfer
                    {
                        KeyWord = request.KeyWord,
                        EmoticonByteArray = imgArray,
                        Extension = Path.GetExtension(path),
                        SenderID = CurrentUserInfo.UserID
                    };
                    if (imgArray.Length < 50000)
                    {
                        Connection.SendCommand(ce, ClientCommands.CustomEmoticon);
                    }
                    else //We need to send in chunks
                    {
                        string data = JsonConvert.SerializeObject(ce);
                        List<string> chunks = SharedStaticMethods.SplitLongMessage(data, 50000); //We send in chunks of 50KB
                        int i = 1;
                        string id = new ShortGuid(Guid.NewGuid());
                        foreach (string chunk in chunks)
                        {
                            FileSlice slice = new FileSlice() { Data = chunk, SliceCount = chunks.Count, SliceNumber = i, TransferID = id, TransferType = FileTransferType.CustomEmoticon };
                            Connection.SendCommand(slice, ClientCommands.FileTransfer);
                            i++;
                        }
                    }
                }
                else
                    Logger.Error("Emoticon request failed, emoticon not found for key :" + request.KeyWord);
            }
            catch (Exception ex)
            {
                Logger.ErrorException("Error sending emoticon to server", ex);
            }
        }

        void connection_AvatarReceived(object sender, AvatarEventArgs e)
        {
            AvatarTransfer avatar = e.Avatar;
            if (avatar.AvatarByteArray == null)
                return;
            SaveAvatarToDisk(ContactClients[e.Avatar.SenderID], e.Avatar.AvatarByteArray, e.Avatar.Hash, e.Avatar.Extension);
        }

        public void SaveAvatarToDisk(UserInfo senderUser, byte[] data, string hash, string extension)
        {
            var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];
            string savePath = ProgramSettings.Instance.AvatarFolder;
            if (!Directory.Exists(savePath))
                Directory.CreateDirectory(savePath);


            savePath = StaticMethods.CombinePath(savePath, senderUser.UserName);
            if (!Directory.Exists(savePath))
                Directory.CreateDirectory(savePath);

            savePath = StaticMethods.CombinePath(savePath, hash + extension);
            try
            {
                File.Delete(savePath);
                StaticMethods.SaveImageData(data, savePath);
                if (File.Exists(savePath))
                {
                    Messenger.Default.Send(new NotificationMessageAction<IContact>(this, senderUser.UserID, "GetContactFromUserID", contact =>
                        {
                            if (contact != null)
                            {
                                contact.Avatar = StaticMethods.BitmapFromUri(new Uri(savePath));
                            }
                        }));
                }
            }
            catch (IOException err)
            {
                if (viewModelLocator.ViewModelMainWindow.CurrentActiveViewModel != null)
                    this.DisplayGroupMessage(CurrentUserInfo.UserID, String.Format("Error saving avatar from {0} : ", senderUser.UserName) + err.Message, MessageType.Error, DateTime.Now, viewModelLocator.ViewModelMainWindow.CurrentActiveViewModel.ID);
                Logger.ErrorException(String.Format("Error saving avatar from {0} : ", senderUser.UserName), err);
                return;
            }
        }

        void connection_LoginResponseReceived(object sender, LoginResponseEventArgs e)
        {
            PcLoginResponse response = e.LoginResponse as PcLoginResponse;

            if (response.Success)
            {


                IsLoggedIn = true;
                IsUserConnecting = false;

                UserInfo currentUser = new UserInfo(response.Username, DeviceID)
                {
                    Avatar = response.UserSettings.Avatar,
                    ChatGroups = response.ClientList.ChatGroups,
                    ClientStatus = ClientStatus.Available,
                    ClientVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(),
                    Color = response.UserSettings.FontHexColor,
                    CustomEmoticons = response.UserSettings.CustomEmoticons,
                    FontFamily = response.UserSettings.FontFamily,
                    FontStyle = response.UserSettings.FontStyle,
                    FontWeight = response.UserSettings.FontWeight,
                    NickName = response.UserSettings.NickName
                };
                _loggedInUserID = currentUser.UserID;//response.User.UserID;
                this.SetUserSettings(response.UserSettings as PcUserSettings);
                App.Current.Dispatcher.Invoke(() => this.AddOrUpdateUsers(currentUser));

                ProcessClientList(response.ClientList);
                Connection.SendMessage(new ChatMessage(this.CurrentUserInfo, MessageType.ClientReady));

                var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];
                viewModelLocator.ViewModelLoginControl.RunOnNetworkConnect();

                InitSpotifyApi();

                if (CurrentUserInfo.ChatGroups == null)
                    CurrentUserInfo.ChatGroups = new List<ChatGroup>();

                //  StartEmoticonExistsCheckAsync(GetEmoticons(CurrentUserInfo.UserID, false, true, false));

                App.Current.Dispatcher.BeginInvoke((Action)(() =>
                {
                    StartStatusTimer();
                    StartReceiptTimer();
                    if (ProgramSettings.Instance.JustUpdated)
                    {
                        string emoticonPath = StaticMethods.CombinePath(ProgramSettings.Instance.CustomEmoticonFolder, CurrentUserInfo.UserName);
                        if (!Directory.Exists(emoticonPath))
                            Directory.CreateDirectory(emoticonPath);

                        string[] files = Directory.GetFiles(ProgramSettings.Instance.CustomEmoticonFolder, "*.*", SearchOption.TopDirectoryOnly);
                        foreach (string file in files)
                        {
                            FileInfo fi = new FileInfo(file);
                            string path = StaticMethods.CombinePath(emoticonPath, fi.Name);
                            if (!File.Exists(path))
                                fi.CopyTo(path);
                        }
                    }

                    viewModelLocator.ViewModelContactList.RunOnNetworkConnect();
                    viewModelLocator.ViewModelMainWindow.RunOnNetworkConnect();

                    foreach (ViewModelChatWindow win in this.OpenWindowViewModels.Values)
                    {
                        win.RunOnNetworkConnect();
                    }

                    if (!this._firstConnect)
                    {
                        foreach (var vm in viewModelLocator.ViewModelMainWindow.ChatViewModels.Where(x => x.GetType() == typeof(ViewModelGroupChat)))
                        {
                            ((ViewModelGroupChat)vm).ChatLog.IncrementSessionID();
                        }
                    }
                    this._firstConnect = false;

                    this.DisplayGroupMessage(CurrentUserInfo, CurrentUserInfo.PrettyName + " has been added to the conversation.", DateTime.Now);
                    LastConnectedUserName = CurrentUserInfo.UserName;


                    //Messenger.Default.Send(new NotificationMessage<double>(this, Properties.Settings.Default.Width, "SetWindowWidth"));
                    //Messenger.Default.Send(new NotificationMessage<double>(this, Properties.Settings.Default.Height, "SetWindowHeight"));
                }));



            }
            else
            {
                StopReconnectRetrying = true;
                _loggedInUserID = string.Empty;

                Messenger.Default.Send(new NotificationMessage<string>(response.Error, "SetLoginStatusText"));
            }
        }

        void Connection_DeviceStatusReceived(object sender, DeviceStatusEventArgs e)
        {
            DeviceStatus status = e.Status;
            UserInfo user;
            if (ContactClients.TryGetValue(status.UserID, out user))
            {
                user.ClientStatus = status.ClientStatus.GetValueOrDefault(ClientStatus.Available);
                user.TimestampAfk = status.TimeStampAfk;
                Application.Current.Dispatcher.BeginInvoke(() => AddOrUpdateUsers(user));
            }
        }


        void connection_NotificationListReceived(object sender, NotificationListEventArgs e)
        {
            if (e.NotificationList != null)
            {
                App.Current.Dispatcher.BeginInvoke(() =>
                    {
                        var notificationList = e.NotificationList;
                        var friendInviteNotifications = notificationList.FriendInviteNotifications.Cast<NotificationBase>();
                        var groupInviteNotifications = notificationList.GroupInviteNotifications.Cast<NotificationBase>();
                        var serverMessageNotifications = notificationList.ServerMessageNotifications.Cast<NotificationBase>();
                        var updateNotifications = notificationList.UpdateAvailableNotifications.Cast<NotificationBase>();

                        IEnumerable<NotificationBase> notifications = friendInviteNotifications.Union(groupInviteNotifications).Union(serverMessageNotifications).Union(updateNotifications);
                        Messenger.Default.Send(new NotificationMessage<IEnumerable<NotificationBase>>(this, notifications, "AddNotificationList"));
                        //if (VmHelper.Locator.ViewModelApplicationUpdate.AvailableUpdate != null)
                        //{
                        //   Messenger.Default.Send(new NotificationMessage<Updater>(this, VmHelper.Locator.ViewModelApplicationUpdate.AvailableUpdate, "NewApplicationVersionFound"));
                        if (updateNotifications.Count() > 0)
                        {
                            Messenger.Default.Send(new NotificationMessageAction<Updater>("GetAvailableUpdate", update =>
                            {
                                if (update != null)
                                    Messenger.Default.Send(new NotificationMessage<Updater>(this, update, "NewApplicationVersionFound"));
                            }));
                        }
                        //    }
                    });
            }
        }


        public void InitSpotifyApi()
        {
            if (UserSettings != null && !UserSettings.EnableSpotifyNowPlaying.GetValueOrDefault() == true)
                return;
            if (_initSpotifyThread != null && _initSpotifyThread.IsAlive)
                return;

            _initSpotifyThread = new Thread(() =>
                {
                    try
                    {
                        SpotifyAPI api = new SpotifyAPI(SpotifyAPI.GetOAuth(), "JiveMessenger.spotilocal.com");
                        Responses.CFID cfid = api.CFID; //It's required to get the contents of API.CFID before doing anything, even if you're not intending to do anything with the CFID
                        if (cfid.error != null)
                        {
                            Logger.Warn(string.Format("Spotify returned a error {0} (0x{1})", cfid.error.message, cfid.error.type));
                            return;
                        }
                        Responses.Status Current_Status = api.Status;
                        if (cfid.error != null)
                        {
                            Logger.Warn(string.Format("Spotify returned a error {0} (0x{1})", cfid.error.message, cfid.error.type));
                            return;
                        }
                        this.SpotifyApi = api;
                    }
                    catch (Exception e)
                    {
                        Logger.WarnException("Error initializing spotify api", e);
                    }
                }) { IsBackground = true };
            _initSpotifyThread.Start();
        }
        private void StartReceiptTimer()
        {
            _receiptTimer = new System.Timers.Timer(3000);
            _receiptTimer.Elapsed += _receiptTimer_Tick;
            _receiptTimer.Start();
        }
        private void StopReceiptTimer()
        {
            if (_receiptTimer != null)
            {
                _receiptTimer.Stop();
                _receiptTimer.Dispose();
            }
        }
        private void StartStatusTimer()
        {
            _statusTimer = new System.Timers.Timer(1000);
            _statusTimer.Elapsed += StatusTimer_Tick;
            _statusTimer.Start();

        }
        private void StopStatusTimer()
        {
            if (_statusTimer != null)
            {
                _statusTimer.Stop();
                _statusTimer.Dispose();
            }
        }
        [DllImport("user32.dll")]
        private static extern bool GetLastInputInfo(out LASTINPUTINFO plii);

        // ReSharper disable InconsistentNaming
        [StructLayout(LayoutKind.Sequential)]
        private struct LASTINPUTINFO
        {
            public static readonly int SizeOf =
                Marshal.SizeOf(typeof(LASTINPUTINFO));

            [MarshalAs(UnmanagedType.U4)]
            public int cbSize;
            [MarshalAs(UnmanagedType.U4)]
            public int dwTime;
        }
        private void StatusTimer_Tick(object sender, EventArgs e)
        {
            System.Timers.Timer timer = (System.Timers.Timer)sender;
            timer.Enabled = false;
            try
            {
                if (this.UserSettings.ManualClientStatus.HasValue && this.UserSettings.ManualClientStatus.Value != ClientStatus.Available)
                    return;
                int idleTime = 0;
                const int delaySeconds = 120;
                LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
                lastInputInfo.cbSize = Marshal.SizeOf(lastInputInfo);
                lastInputInfo.dwTime = 0;

                int envTicks = Environment.TickCount;

                if (GetLastInputInfo(out lastInputInfo))
                {
                    int lastInputTick = lastInputInfo.dwTime;
                    idleTime = envTicks - lastInputTick;
                }

                bool sendAfkUpdate = false;
                bool sendBusyUpdate = false;

                int seconds = idleTime / 1000;
                if (seconds > delaySeconds && !IsAfk)
                {
                    IsAfk = true;
                    sendAfkUpdate = true;
                }
                else if (IsAfk && seconds < delaySeconds)
                {
                    IsAfk = false;
                    sendAfkUpdate = true;
                }

                bool busy = ScreenDetection.IsApplicationFullScreen();
                if ((!IsBusy && busy) || IsBusy && !busy)
                {
                    if (!IsAfk || !busy)//let AFK override busy.
                        sendBusyUpdate = true;
                    IsBusy = busy;
                }



                if (sendBusyUpdate || sendAfkUpdate)
                {
                    if (sendBusyUpdate)
                    {
                        CurrentUserInfo.ClientStatus = IsBusy ? ClientStatus.Busy : ClientStatus.Available;
                    }
                    else if (sendAfkUpdate)
                    {
                        CurrentUserInfo.ClientStatus = IsAfk ? ClientStatus.Afk : ClientStatus.Available;
                    }
                    Connection.SendCommand(new DeviceStatus(CurrentUserInfo.UserID, CurrentUserInfo.ClientStatus), ClientCommands.DeviceStatusUpdate);
                    Application.Current.Dispatcher.BeginInvoke(() => AddOrUpdateUsers(CurrentUserInfo));
                }
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error in status timer tick", err);
            }
            finally
            {
                timer.Enabled = true;
            }
        }
        private void Connection_ChatMessageSendt(object sender, ChatMessageEventArgs e)
        {
            ChatMessage msg = e.Message;
            if (msg.MessageType == MessageType.GroupMessage || msg.MessageType == MessageType.PrivateMessage)
            {
                SentMessages.Add(msg.MessageID.Value, new KeyValuePair<DateTime, ChatMessage>(DateTime.Now, msg));
            }
        }
        void Connection_ServerInfoMessageReceived(object sender, ServerInfoMessageEventArgs e)
        {
            ServerInfoMessage msg = e.Message;
            switch (msg.MessageType)
            {
                case ServerInfoMessageType.UserInfoUpdated:
                    {
                        UserInfo user = msg.UserInfo;
                        App.Current.Dispatcher.BeginInvoke((() => this.AddOrUpdateUsers(user)));
                        break;
                    }
                case ServerInfoMessageType.ContactAuthenticated:
                    {
                        UserInfo user = msg.UserInfo;
                        var locator = (ViewModelLocator)Application.Current.Resources["Locator"];
                        var groupViewModels = locator.ViewModelMainWindow.ChatViewModels.Where(x => x.GetType() == typeof(ViewModelGroupChat));
                        App.Current.Dispatcher.BeginInvoke((Action)(() =>
                        {

                            this.AddOrUpdateUsers(user);
                            //Don't show "has logged on" notice for subsequent logins from the same user.
                            if (ContactClients.Values.Where(x => x.UserName == user.UserName && x.ClientStatus != ClientStatus.Offline).Count() == 1)
                                this.DisplayGroupMessage(user, user.PrettyName + " has been added to the conversation.", DateTime.Now);

                        }));

                        if (!user.Equals(CurrentUserInfo) && user.ClientStatus != ClientStatus.Offline && !IsBusy)
                        {

                            NotifyMessage nMsg = new NotifyMessage("Images/GreenSkin.png", "Jive Messenger", String.Format("{0} has just logged on", user.PrettyName), () => OpenNewPM(user.UserID));

                            Messenger.Default.Send<NotifyMessage>(nMsg);
                        }
                        SetWindowStatus(user.UserID);
                        break;
                    }
                case ServerInfoMessageType.ContactDisconnected:
                    {
                        App.Current.Dispatcher.BeginInvoke((Action)(() => RemoveUser(msg.UserInfo.UserID)));
                        SetWindowStatus(msg.UserInfo.UserID);
                        break;
                    }
            }
        }

        private void Connection_UserSettingsReceived(object sender, UserSettingsEventArgs e)
        {
            this.SetUserSettings(e.Settings);
        }
        private void Connection_ClientListReceived(object sender, ClientListEventArgs e)
        {
            if (!IsLoggedIn)
                return;
            ProcessClientList(e.ClientList);
        }
        private void ProcessClientList(ClientList list)
        {
            List<UserInfo> users = list.Clients;
            this.FriendUsernames = list.FriendUsernames;

            if (_receivedClientList)//We only include our own user if we're not logging in. When logging in, the user is already added.
            {
                this.CurrentUserInfo.ChatGroups = list.ChatGroups;
                users.Insert(0, this.CurrentUserInfo);
            }
            App.Current.Dispatcher.Invoke((() =>
            {
                this.AddOrUpdateUsers(users);
            }));
        }
        private void Connection_ChatReceiptReceived(object sender, ChatReceiptEventArgs e)
        {
            AcknowledgeSendtMessageReceipt(e.Receipt.MessageID);
        }
        private void Connection_ContactEmoticonReceived(object sender, ContactEmoticonEventArgs e)
        {
            ReceiveEmoticon(e.Emoticon);
        }
        private void SetUserSettings(PcUserSettings settings)
        {
            this.UserSettings = settings;
            if (UserSettings.CustomEmoticons == null)
                UserSettings.CustomEmoticons = new HashSet<ContactEmoticon>();
            if (UserSettings.IgnoredContacts == null)
                UserSettings.IgnoredContacts = new List<string>();
            if (UserSettings.OpenChats == null)
                UserSettings.OpenChats = new List<OpenChat>();
            if (UserSettings.BlockedGroups == null)
                UserSettings.BlockedGroups = new List<string>();


            VmHelper.Locator.ViewModelContactList.ManualClientStatus = this.UserSettings.ManualClientStatus.GetValueOrDefault(ClientStatus.Available);
        }
        private void _receiptTimer_Tick(object sender, EventArgs e)
        {
            System.Timers.Timer timer = (System.Timers.Timer)sender;
            timer.Enabled = false;
            try
            {
                //lock (_lockObject)
                //{
                List<int> toRemove = new List<int>();
                foreach (var v in this.SentMessages.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
                {

                    if (DateTime.Now.Subtract(v.Value.Key).TotalSeconds > 6)
                    {
                        App.Current.Dispatcher.BeginInvoke(() =>
                            {
                                ViewModelChat vm = (ViewModelChat)VmHelper.Locator.ViewModelMainWindow.ChatViewModels.FirstOrDefault(x => x is ViewModelChat && x.ID == v.Value.Value.RecipientID);

                                vm.DisplayMessage(new DisplayMessage(JiveContactModel.RedText, String.Format("Message response timeout. It is possible the message was not received: {0}", v.Value.Value.Message), DisplayMessageOptions.ErrorOptions, DateTime.Now));
                            });
                        toRemove.Add(v.Key);
                    }
                }
                foreach (int i in toRemove)
                    this.SentMessages.Remove(i);
                //}
            }
            catch (Exception err)
            {
                Logger.ErrorException("Error in receipt timer tick", err);
            }
            finally
            {
                timer.Enabled = true;
            }
        }
        public void AcknowledgeSendtMessageReceipt(int id)
        {
            this.SentMessages.Remove(id);
        }
        private void Connection_OnDisconnected(object sender, EventArgs e)
        {

            IsLoggedIn = false;

            if (Application.Current == null)
                return;

            var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];
            viewModelLocator.ViewModelContactList.RunOnNetworkDisconnect();
            viewModelLocator.ViewModelMainWindow.RunOnNetworkDisconnect();
            viewModelLocator.ViewModelLoginControl.RunOnNetworkDisconnect();

            ClearConnectedClients();
            this.Cleanup();


            Messenger.Default.Send(new NotificationMessage(this, "SaveWindowState"));

            App.Current.Dispatcher.Invoke(() =>
            {
                foreach (ViewModelPrivateMessageWindow pm in this.OpenWindowViewModels.Values)
                {
                    pm.RunOnNetworkDisconnect();
                }


                StopStatusTimer();
                StopReceiptTimer();
            });
            if (!IsRetrying)
            {
                if (IsUserConnecting)
                {
                    viewModelLocator.ViewModelMainWindow.Cleanup();
                }
                else
                {
                    this.StartReconnectTask();
                }
            }

            IsUserConnecting = false;

        }
        public void StopReconnectTask()
        {
            if (IsRetrying)
                StopReconnectRetrying = true;
        }
        public void StartReconnectTask()
        {
            Debug.WriteLine("Starting reconnect task");
            ClearConnectedClients();
            Task.Factory.StartNew(() =>
            {
                if (Application.Current == null)
                    return;
                var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];
                try
                {
                    for (int count = 0; count < 25; count++)
                    {
                        if (StopReconnectRetrying)
                        {
                            Debug.WriteLine("Stopping reconnect task");
                            return;
                        }
                        IsRetrying = true;
                        int sleepTime = 5 + (5 * count);
                        while (true)
                        {
                            if (StopReconnectRetrying)
                            {

                                //  viewModelLocator.ViewModelLoginControl.LoginControlStatusText = "";
                                //Messenger.Default.Send(new NotificationMessage<string>("", "SetLoginStatusText"));
                                Debug.WriteLine("Stopping reconnect task");
                                return;
                            }
                            //  viewModelLocator.ViewModelLoginControl.LoginControlStatusText = String.Format("Retrying in {0} seconds.", sleepTime.ToString(CultureInfo.InvariantCulture));
                            Messenger.Default.Send(new NotificationMessage<string>(String.Format("Retrying in {0} seconds.", sleepTime.ToString(CultureInfo.InvariantCulture)), "SetLoginStatusText"));
                            if (sleepTime <= 0)
                            {
                                break;
                            }
                            sleepTime--;


                            // Set is never called, so we wait always until the timeout occurs
                            using (EventWaitHandle tmpEvent = new ManualResetEvent(false))
                            {
                                tmpEvent.WaitOne(TimeSpan.FromMilliseconds(1000));
                            }
                        }
                        Messenger.Default.Send(new NotificationMessage<string>(this, "", "SetLoginStatusText"));

                        viewModelLocator.ViewModelLoginControl.IsLoginButtonEnabled = false;
                        try
                        {
                            Debug.WriteLine("Attempting login in reconnect");
                            if (Connection.Login(Properties.Settings.Default.Server, Properties.Settings.Default.Username, Properties.Settings.Default.Password, ProgramSettings.Instance.ServerPort))
                            {
                                _queuedMessages = new Dictionary<int, Dictionary<int, ChatMessage>>();
                                _addedBatchMessages = new Dictionary<int, List<ChatMessage>>();
                                _processedMessageCount = new Dictionary<int, int>();
                                return;
                            }
                            else
                            {
                                Debug.WriteLine("login in reconnect failed");
                                Messenger.Default.Send(new NotificationMessage<string>(this, "Connection failed", "SetLoginStatusText"));
                                // Set is never called, so we wait always until the timeout occurs
                                using (EventWaitHandle tmpEvent = new ManualResetEvent(false))
                                {
                                    tmpEvent.WaitOne(TimeSpan.FromMilliseconds(1500));
                                }

                            }
                        }
                        catch (Exception e)
                        {
                            Logger.ErrorException("Error in reconnect", e);
                        }
                        finally
                        {
                            viewModelLocator.ViewModelLoginControl.IsLoginButtonEnabled = true;
                        }
                    }
                }
                finally
                {
                    if (!IsUserConnecting)
                        viewModelLocator.ViewModelLoginControl.IsLoginButtonEnabled = true;
                    IsRetrying = false;
                    StopReconnectRetrying = false;
                }
            });
        }
        public void StartConnectTask(bool isAutoLogin)
        {
            var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];
            viewModelLocator.ViewModelLoginControl.IsLoginButtonEnabled = false;

            Task t = Task.Factory.StartNew(() =>
            {
                try
                {
                    ClearConnectedClients();
                    if (Properties.Settings.Default.Server != _lastConnectedServer)
                        viewModelLocator.ViewModelMainWindow.Cleanup();

                    Messenger.Default.Send(new NotificationMessage<string>(this, "", "SetLoginStatusText"));
                    if (Connection.Login(Properties.Settings.Default.Server, Properties.Settings.Default.Username, Properties.Settings.Default.Password, ProgramSettings.Instance.ServerPort))
                    {
                        _queuedMessages = new Dictionary<int, Dictionary<int, ChatMessage>>();
                        _addedBatchMessages = new Dictionary<int, List<ChatMessage>>();
                        _processedMessageCount = new Dictionary<int, int>();
                        _lastConnectedServer = Properties.Settings.Default.Server;
                    }
                    else
                    {
                        // viewModelLocator.ViewModelLoginControl.LoginControlStatusText = "Connection failed";
                        Messenger.Default.Send(new NotificationMessage<string>(this, "Connection failed", "SetLoginStatusText"));
                        //viewModelLocator.ViewModelMainWindow.RunOnNetworkDisconnect();
                        if (isAutoLogin && !IsRetrying)
                        {
                            StartReconnectTask();
                        }
                    }
                }
                catch (Exception e)
                {
                    Logger.ErrorException("Error in connect", e);
                }
            });
        }

        /// <summary>
        /// Process received ChatMessage objects
        /// </summary>
        /// <param name="header"></param>
        /// <param name="connection"></param>
        /// <param name="msg"></param>
        private void Connection_ChatMessageReceived(object s, ChatMessageEventArgs e)
        {
            ChatMessage msg = e.Message;
            msg.MessageRelayTime = TimeZoneInfo.ConvertTimeFromUtc(msg.MessageRelayTime.Value, TimeZoneInfo.Local);
            switch (msg.MessageType)
            {
                case MessageType.GroupMessage:
                    {
                        App.Current.Dispatcher.Invoke(() => DisplayGroupMessage(msg));
                        if (VmHelper.Locator.ViewModelMainWindow.ChatViewModels.Any(x => x.ID == msg.RecipientID))
                        {
                            bool isApplicationActive = StaticMethods.ApplicationIsActivated();
                            if (!isApplicationActive && Properties.Settings.Default.SettingFlashWindowOnNewMessage)//&& !IsFlashing)
                                Messenger.Default.Send<NotificationMessage>(new NotificationMessage("FlashMainWindow"));
                            if (VmHelper.Locator.ViewModelMainWindow.CurrentActiveViewModel.ID != msg.RecipientID || !isApplicationActive)
                            {
                                if (Properties.Settings.Default.SettingEnableSounds && Properties.Settings.Default.SettingEnableGroupMessageNotificationSound)
                                {
                                    PlayNewMessageSound();
                                }
                            }
                        }
                        break;
                    }
                case MessageType.PrivateMessage:
                    {
                        App.Current.Dispatcher.Invoke((Action)(() => this.DisplayPrivateMessage(msg, true)));
                        bool isApplicationActive = StaticMethods.ApplicationIsActivated();
                        if (!isApplicationActive && Properties.Settings.Default.SettingFlashWindowOnNewMessage)//&& !IsFlashing)
                            Messenger.Default.Send<NotificationMessage>(new NotificationMessage("FlashMainWindow"));

                        if (VmHelper.Locator.ViewModelMainWindow.CurrentActiveViewModel.ID != msg.SenderUsername || !isApplicationActive)
                        {
                            if (Properties.Settings.Default.SettingEnableSounds && Properties.Settings.Default.SettingEnablePrivateMessageNotificationSound)
                                PlayNewMessageSound();
                        }

                        break;
                    }
                case MessageType.IsWriting:
                    {
                        UpdateNowWriting(msg);
                        break;
                    }
                case MessageType.Nudge:
                    {
                        if (!UserSettings.EnableNudge.GetValueOrDefault())
                            break;

                        App.Current.Dispatcher.BeginInvoke((Action)(() =>
                        {
                            var locator = VmHelper.Locator;

                            if (!StaticMethods.ApplicationIsActivated() && Properties.Settings.Default.SettingFlashWindowOnNewMessage)
                            {
                                Messenger.Default.Send<NotificationMessage>(new NotificationMessage("FlashMainWindow"));
                            }

                            if (msg.Message == "PM")
                            {
                                this.DisplayPrivateMessage(msg, true);
                            }
                            else
                            {
                                if (msg.SenderUsername != CurrentUserInfo.UserName)
                                {
                                    var contact = locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID);
                                    this.DisplayGroupMessage(msg.SenderID, string.Format("{0} just sent you a nudge.", contact.PrettyName), MessageType.Nudge, DateTime.Now, msg.RecipientID);
                                    string message = String.Format("{0} just sent you a nudge.", contact.PrettyName);
                                    NotifyMessage nMsg = new NotifyMessage("Images/GreenSkin.png", "Jive Messenger", message, () => OpenNewPM(msg.SenderID));

                                    Messenger.Default.Send<NotifyMessage>(nMsg);

                                    if (locator.ViewModelMainWindow.CurrentActiveViewModel is ViewModelChat)
                                        ((ViewModelChat)locator.ViewModelMainWindow.CurrentActiveViewModel).IsInputTextFocused = true;

                                }
                                Messenger.Default.Send<NotificationMessage>(new NotificationMessage("Nudge"));
                            }
                            if (Properties.Settings.Default.SettingEnableSounds && Properties.Settings.Default.SettingEnableNudgeNotificationSound)
                            {
                                PlayNudgeSound();
                            }
                        }));
                        break;
                    }
            }
        }


        public void PlayNudgeSound()
        {
            Uri uri = new Uri("pack://application:,,,/JiveMessenger;component/Resources/Sounds/nudge.wav");
            SoundPlayer player = new SoundPlayer(Application.GetResourceStream(uri).Stream);
            player.Play();
        }
        public void PlayNewMessageSound()
        {
            if (_isPlayingNotificationSound)
                return;
            Task.Factory.StartNew(() =>
                {
                    try
                    {
                        _isPlayingNotificationSound = true;
                        Uri uri = new Uri("pack://application:,,,/JiveMessenger;component/Resources/Sounds/alert.wav");
                        SoundPlayer player = new SoundPlayer(Application.GetResourceStream(uri).Stream);
                        player.PlaySync();
                    }
                    finally
                    {
                        _isPlayingNotificationSound = false;
                    }

                });


        }

        /// <summary>
        /// Sets the online/offline status of open windows
        /// </summary>
        /// <param name="users"></param>
        /// <param name="isOffline"></param>
        private void SetWindowStatus(string userID)
        {
            UserInfo user;
            if (!ContactClients.TryGetValue(userID, out user))
                return;
            ViewModelChatWindow pm;
            if (OpenWindowViewModels.TryGetValue(user.UserName, out pm))
            {
                bool isOffline = user.ClientStatus == ClientStatus.Offline;
                pm.SetStatus(isOffline);
            }
        }

        /// <summary>
        /// Saves info on clients who are currently typing
        /// </summary>
        private void UpdateNowWriting(ChatMessage msg)
        {
            if (CurrentUserInfo == null)
                return;
            UserInfo user = null;
            if (!ContactClients.TryGetValue(msg.SenderID, out user))
                return;
            if (user.UserName == CurrentUserInfo.UserName)
                return;

            var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];
            IContact sender = VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID);
            foreach (ViewModelChat vm in viewModelLocator.ViewModelMainWindow.ChatViewModels.Where(vm => vm.ID == msg.RecipientID))// sender.ChatGroups.Any(g => g.ID == vm.ID) || sender.UserName == vm.ID))
            {
                vm.LastReceivedMessageTyping[msg.SenderID] = DateTime.Now;
            }
        }

        /// <summary>
        /// Receives and processes a private message.
        /// </summary>
        /// <param name="msg"></param>
        private void DisplayPrivateMessage(ChatMessage msg, bool showNotifyMessage)
        {
            var locator = VmHelper.Locator;
            UserInfo target = msg.SenderUsername == CurrentUserInfo.UserName ? ContactClients.Values.FirstOrDefault(x => x.UserName == msg.RecipientID) : ContactClients[msg.SenderID];
            var contact = locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserName == target.UserName);

            ViewModelPrivateMessage vm = (ViewModelPrivateMessage)locator.ViewModelMainWindow.ChatViewModels.Union(this.ClosedConversations.Values).Where(x => x.ID == contact.UserName).FirstOrDefault();
            string notifyMessage = String.Format("{0} says:\n {1}", target.PrettyName, msg.Message);
            if (msg.MessageType == MessageType.Nudge)
            {
                notifyMessage = String.Format("{0} just sent you a nudge.", target.PrettyName);
            }
            if (vm == null)
            {
                vm = new ViewModelPrivateMessage(this, contact, locator.ViewModelContactList.CurrentUser, true, false);

                locator.ViewModelMainWindow.ChatViewModels.Add(vm);
            }
            if (msg.SenderID != CurrentUserInfo.UserID)
            {

                if (msg.MessageType == MessageType.Nudge)
                {
                    Messenger.Default.Send(new NotificationMessage(vm, "Nudge"));
                }
                if (showNotifyMessage
                    && locator.ViewModelMainWindow.CurrentActiveViewModel is ViewModelChat
                    && (((ViewModelChat)locator.ViewModelMainWindow.CurrentActiveViewModel).ID != target.UserName
                    || !((ViewModelChat)locator.ViewModelMainWindow.CurrentActiveViewModel).IsFocused))
                {
                    NotifyMessage nMsg = new NotifyMessage("Images/GreenSkin.png", "Jive Messenger", notifyMessage, () => OpenNewPM(target.UserID));

                    Messenger.Default.Send<NotifyMessage>(nMsg);

                    if (((ViewModelChat)locator.ViewModelMainWindow.CurrentActiveViewModel).IsFocused)
                        ((ViewModelChat)locator.ViewModelMainWindow.CurrentActiveViewModel).IsInputTextFocused = true;
                }

            }
            if (!locator.ViewModelMainWindow.ChatViewModels.Any(x => x.ID == vm.ID))
            {
                locator.ViewModelMainWindow.ChatViewModels.Add(vm);
            }
            if (!this.OpenWindowViewModels.ContainsKey(target.UserName)
                && (PrivateMessageBehavior == PrivateMessageBehavior.Both
                || PrivateMessageBehavior == PrivateMessageBehavior.Windows))
            {
                Messenger.Default.Send(new NotificationMessage<ViewModelChat>(this, vm, "OpenChatWindow"));
            }

            DisplayChatMessage(msg, vm);
            //var displayMessageOptions = DisplayMessageOptions.ChatMessageOption;
            //if (msg.MessageType == MessageType.Nudge)
            //{
            //    displayMessageOptions = DisplayMessageOptions.NudgeOptions;
            //    msg.Message = String.Format("{0} just sent you a nudge.", target.PrettyName);
            //}

            //vm.DisplayMessage(new DisplayMessage(VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID), msg.Message, displayMessageOptions, msg.MessageRelayTime.Value));
        }

        /// <summary>
        /// Opens a private message window to the specified target user
        /// </summary>
        /// <param name="target"></param>
        public void OpenNewPM(string userID)
        {
            var locator = (ViewModelLocator)Application.Current.Resources["Locator"];

            var contact = locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == userID);
            var behavior = this.PrivateMessageBehavior;

            ViewModelPrivateMessage vm = (ViewModelPrivateMessage)locator.ViewModelMainWindow.ChatViewModels.Union(this.ClosedConversations.Values).Where(x => x.ID == contact.UserName).FirstOrDefault();
            if (vm == null)
            {
                vm = new ViewModelPrivateMessage(this, contact, VmHelper.Locator.ViewModelContactList.CurrentUser, true, true);
            }
            if (!locator.ViewModelMainWindow.ChatViewModels.Contains(vm))
                locator.ViewModelMainWindow.ChatViewModels.Add(vm);
            if (behavior == PrivateMessageBehavior.Tabs || behavior == PrivateMessageBehavior.Both)
                locator.ViewModelMainWindow.CurrentActiveViewModel = vm;


            if (behavior == PrivateMessageBehavior.Both || behavior == PrivateMessageBehavior.Windows)
            {
                if (!this.OpenWindowViewModels.ContainsKey(vm.ID))
                    Messenger.Default.Send(new NotificationMessage<ViewModelChat>(this, vm, "OpenChatWindow"));
                else
                    Messenger.Default.Send(new NotificationMessage(vm, "ActivateWindow"));
            }
        }

        public void ChangeManualClientStatus(ClientStatus status)
        {
            if (CurrentUserInfo == null)
                return;
            UserSettings.ManualClientStatus = status;
            CurrentUserInfo.ClientStatus = status;
            AddOrUpdateUsers(CurrentUserInfo);
            Connection.SendCommand(new DeviceStatus(this.CurrentUserInfo.UserID, status), ClientCommands.DeviceStatusUpdate);
        }

        public void DownloadApplicationUpdate(string downloadUrl)
        {
            Updater.UpdateCompleteEvent += Updater_UpdateCompleteEvent;
            Updater.UpdateDownloadProgressChanged += Updater_UpdateDownloadProgressChanged;
            Updater.DownloadUpdate(downloadUrl);
            Messenger.Default.Send(new NotificationMessage<string>(this, "Downloading update", "OpenUpdateProgressWindow"));

        }

        public void SkipUpdateVersion(string version)
        {
            Messenger.Default.Send(new NotificationMessage<Updater>(this, null, "NewApplicationVersionFound"));
            Properties.Settings.Default.SkipUpdateVersion = version;
        }

        void Updater_UpdateDownloadProgressChanged(object sender, Updater.UpdateDownloadProgressEventArgs e)
        {
            Messenger.Default.Send(new NotificationMessage<int>(this, e.Percent, "SetUpdateDownloadProgress"));
        }

        void Updater_UpdateCompleteEvent(object sender, Updater.UpdateCompletedEventArgs e)
        {
            Updater.UpdateCompleteEvent -= Updater_UpdateCompleteEvent;
            Updater.UpdateDownloadProgressChanged -= Updater_UpdateDownloadProgressChanged;

            if (e.Success)
            {
                Messenger.Default.Send(new NotificationMessage<Updater.UpdateCompletedEventArgs>(this, e, "UpdateCompleted"));
            }
        }

        private void AddOrUpdateUsers(UserInfo user, bool processEmoticonAndAvatar = true)
        {
            if (user == null)
                return;
            if (user.ChatGroups == null)
                user.ChatGroups = new List<ChatGroup>();
            if (user.CustomEmoticons == null)
                user.CustomEmoticons = new HashSet<ContactEmoticon>();

            if (user.TimestampAfk.HasValue)
            {
                if (user.TimestampAfk.Value.Offset.TotalMinutes == 0)
                    user.TimestampAfk = user.TimestampAfk.Value.ToLocalTime();
            }

            if (user.TimestampOffline.HasValue)
            {
                if (user.TimestampOffline.Value.Offset.TotalMinutes == 0)
                    user.TimestampOffline = user.TimestampOffline.Value.ToLocalTime();
            }
            UserInfo oldUser;

            if (ContactClients.TryGetValue(user.UserID, out oldUser))
            {
                oldUser.ClientStatus = user.ClientStatus;
                //this means we have it, so we just go through the properties and update what we can.
                var userInfoProperties = oldUser.GetType().GetProperties().Where(prop => prop.CanRead && prop.CanWrite);
                foreach (var prop in userInfoProperties)
                {
                    var value = prop.GetValue(user, null);
                    var attributes = prop.GetCustomAttributes(typeof(UserInfoAttribute), false);
                    if (attributes.Length > 0)
                    {
                        UserInfoAttribute att = attributes[0] as UserInfoAttribute;
                        if (att == null)
                            continue;

                        else if (value != null)
                        {
                            try
                            {
                                prop.SetValue(oldUser, value, null);
                            }
                            catch (Exception err)
                            {
                                Logger.ErrorException("Error setting user settings property", err);
                            }
                        }
                    }
                }
            }
            else
                ContactClients.GetOrAdd(user.UserID, user);

            var viewModelLocator = VmHelper.Locator;


            if (user.Equals(this.CurrentUserInfo))
            {
                this.UserSettings.CustomEmoticons = user.CustomEmoticons;
            }

            viewModelLocator.ViewModelContactList.AddOrUpdateContact(user, processEmoticonAndAvatar);
            if (processEmoticonAndAvatar)
            {
                if (user.ClientStatus != ClientStatus.Offline)
                    ProcessCustomContactEmoticons(user);

                ProcessAvatar(user);
            }
        }

        private void AddOrUpdateUsers(IEnumerable<UserInfo> users)
        {
            List<string> performedEmoticonCheck = new List<string>();
            foreach (UserInfo user in users)
            {
                bool checkEmoticons = !performedEmoticonCheck.Contains(user.UserName);
                AddOrUpdateUsers(user, checkEmoticons);
                if (checkEmoticons)
                    performedEmoticonCheck.Add(user.UserName);
                SetWindowStatus(user.UserID);
            }
            List<UserInfo> connectedClients = new List<UserInfo>(this.ContactClients.Values);
            var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];

            foreach (var user in connectedClients)
            {
                if (!users.Contains(user) && !user.Equals(CurrentUserInfo))
                {
                    this.ContactClients.Remove(user.UserID);
                    viewModelLocator.ViewModelContactList.Contacts.Remove(viewModelLocator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == user.UserID));
                }
            }
            if (!_receivedClientList)
            {
                viewModelLocator.ViewModelMainWindow.RestoreTabs();
                Messenger.Default.Send(new NotificationMessage(this, "RefreshContactsFilter"));
                this._receivedClientList = true;
            }
        }

        private void RemoveUser(string userID)
        {
            var viewModelLocator = VmHelper.Locator;
            UserInfo user = null;
            if (ContactClients.TryGetValue(userID, out user))
            {
                // don't remove our own connected user.
                if (!userID.Equals(CurrentUserInfo.UserID))
                {
                    //ContactClients.Remove(user.UserID);
                    Messenger.Default.Send(new NotificationMessage<string>(this, user.UserID, "SetContactAsOffline"));
                    // viewModelLocator.ViewModelContactList.SetContactAsOffline(user.UserID);
                }
                var chats = viewModelLocator.ViewModelMainWindow.ChatViewModels.Union(this.ClosedConversations.Values).Where(x => x is ViewModelChat && (user.ChatGroups.Any(y => y.ID == x.ID) || x.ID == user.ID)).Cast<ViewModelChat>();
                foreach (var vm in chats)
                {
                    Messenger.Default.Send(new NotificationMessageAction<IContact>(this, userID, "GetContactFromUserID", contact =>
                        {
                            if (contact != null)
                            {
                                vm.DisplayMessage(new DisplayMessage(contact, user.PrettyName + " has left the conversation!", DisplayMessageOptions.LogoutOptions, DateTime.Now));
                            }
                        }));
                }
            }
        }

        /// <summary>
        /// Receives a custom contact emoticon and saves it to disk
        /// </summary>
        /// <param name="msg"></param>
        private void ReceiveEmoticon(ContactEmoticonTransfer ce)
        {
            if (String.IsNullOrEmpty(ce.KeyWord))
                return;
            var viewModelLocator = (ViewModelLocator)Application.Current.Resources["Locator"];
            string savePath = ce.SenderID == this.CurrentUserInfo.UserID ? ProgramSettings.Instance.CustomEmoticonFolder : ProgramSettings.Instance.CustomContactEmoticonFolder;
            if (!Directory.Exists(savePath))
                Directory.CreateDirectory(savePath);

            UserInfo sender = ContactClients[ce.SenderID];
            savePath = StaticMethods.CombinePath(savePath, sender.UserName);
            if (!Directory.Exists(savePath))
                Directory.CreateDirectory(savePath);

            savePath = StaticMethods.CombinePath(savePath, ce.Hash + ce.Extension);
            try
            {
                File.Delete(savePath);
                StaticMethods.SaveImageData(ce.EmoticonByteArray, savePath);
            }
            catch (IOException err)
            {
                if (viewModelLocator.ViewModelMainWindow.CurrentActiveViewModel != null)
                    this.DisplayGroupMessage(CurrentUserInfo.UserID, String.Format("Error saving custom emoticon from {0} : ", sender.UserName) + err.Message, MessageType.Error, DateTime.Now, viewModelLocator.ViewModelMainWindow.CurrentActiveViewModel.ID);
                Logger.ErrorException(String.Format("Error saving custom emoticon from {0} : ", sender.UserName), err);
                return;
            }

            Task.Factory.StartNew(() =>
            {
                App.Current.Dispatcher.BeginInvoke(() =>
                    {
                        Messenger.Default.Send(new NotificationMessage(viewModelLocator.ViewModelMainWindow.CurrentActiveViewModel, "ScrollDocument"));
                    });

                // Set is never called, so we wait always until the timeout occurs
                using (EventWaitHandle tmpEvent = new ManualResetEvent(false))
                {
                    tmpEvent.WaitOne(TimeSpan.FromMilliseconds(500));
                }

                //We run a scroll event on the chat control again after a small delay. If we do not do this, the page might scroll before the emoticons render,
                //and the auto scroll on new message will stop functioning
                App.Current.Dispatcher.BeginInvoke(() =>
                {
                    Messenger.Default.Send(new NotificationMessage(viewModelLocator.ViewModelMainWindow.CurrentActiveViewModel, "ScrollDocument"));
                });
            });
        }

        private void ProcessAvatar(UserInfo user)
        {
            if (user.Avatar == null)
                return;
            Logger.Info(string.Format("Processing avatar for user {0}", user.UserName));
            FileInfo file = new FileInfo(StaticMethods.CombinePath(ProgramSettings.Instance.AvatarFolder, user.UserName, user.Avatar.Hash + user.Avatar.Extension));
            try
            {
                bool requestAvatar = !String.IsNullOrEmpty(user.Avatar.Hash);
                if (File.Exists(file.FullName))
                {
                    string hash = file.GetMD5();

                    if (hash == user.Avatar.Hash)
                        requestAvatar = false;
                }


                if (requestAvatar)
                {
                    Connection.SendCommand(new AvatarRequest(user.Avatar.Hash, user.Avatar.Extension, user.ID, user.UserName), ClientCommands.RequestAvatar);
                    Logger.Info(string.Format("requesting avatar download for user {0}", user.UserName));
                }
            }
            catch (Exception ex)
            {
                Logger.ErrorException("Error checking if emoticon file exists", ex);
            }


        }

        /// <summary>
        /// Process custom emoticons, and request missing images.
        /// </summary>
        /// <param name="message"></param>
        private void ProcessCustomContactEmoticons(UserInfo user)
        {
            if (user.CustomEmoticons == null)
                return;

            Task.Factory.StartNew(() =>
                {
                    foreach (ContactEmoticon ce in user.CustomEmoticons)
                    {
                        bool requestEmoticon = false;
                        string keyword = ce.Keyword;

                        requestEmoticon = !ValidateEmoticonFileExists(ce, user);
                        if (requestEmoticon)
                            requestEmoticon = TryGetEmoticonFromOtherUsersCache(ce, user);
                        if (requestEmoticon)
                        {
                            //Send a message to the server requesting the emoticon so we can save it to disk
                            Connection.SendCommand(new EmoticonRequest(keyword, user.ID, user.UserName), ClientCommands.RequestEmoticon);

                            // Set is never called, so we wait always until the timeout occurs
                            using (EventWaitHandle tmpEvent = new ManualResetEvent(false))
                            {
                                tmpEvent.WaitOne(TimeSpan.FromMilliseconds(3));
                            }
                        }
                    }
                });
        }
        /// <summary>
        /// Checks if the emoticon is stored on disk.
        /// </summary>
        /// <param name="ce"></param>
        /// <param name="user"></param>
        /// <returns>Returns true if the emoticon file exists, else false</returns>
        private bool ValidateEmoticonFileExists(ContactEmoticon ce, UserInfo user)
        {
            string folder = user.Equals(this.CurrentUserInfo) ? ProgramSettings.Instance.CustomEmoticonFolder : ProgramSettings.Instance.CustomContactEmoticonFolder;
            FileInfo file = new FileInfo(StaticMethods.CombinePath(folder, user.UserName, ce.Hash + ce.Extension));
            try
            {
                if (!File.Exists(file.FullName))
                    return false;
                else
                {
                    string hash = file.GetMD5();
                    if (hash == ce.Hash)
                        return true;
                    else
                        return false;
                }
            }
            catch (Exception ex)
            {
                Logger.ErrorException("Error checking if emoticon file exists", ex);
                return false;
            }
        }
        /// <summary>
        /// Tries to copy the emoticon from another user.
        /// </summary>
        /// <param name="ce"></param>
        /// <param name="user"></param>
        /// <returns></returns>
        private bool TryGetEmoticonFromOtherUsersCache(ContactEmoticon ce, UserInfo user)
        {
            DirectoryInfo dir = new DirectoryInfo(ProgramSettings.Instance.CustomContactEmoticonFolder);
            FileInfo[] files = dir.GetFiles(ce.Extension, SearchOption.AllDirectories);

            FileInfo file = files.FirstOrDefault(x => x.Name == ce.Hash + ce.Extension);
            try
            {
                if (!File.Exists(file.FullName))
                    return false;
                else
                {
                    string hash = file.GetMD5();
                    if (hash == ce.Hash)
                    {
                        file.CopyTo(StaticMethods.CombinePath(ProgramSettings.Instance.CustomEmoticonFolder, user.UserName, ce.Hash + ce.Extension));
                        return true;
                    }
                    else
                        return false;
                }
            }
            catch (Exception ex)
            {
                Logger.ErrorException("Error checking if emoticon file exists", ex);
                return false;
            }
        }

        private void DisplayGroupMessage(string senderID, string messageText, MessageType mt, DateTime timestamp, string recipientID)
        {
            JiveContactModel sender = VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x is JiveContactModel && x.UserID == senderID) as JiveContactModel;
            ChatMessage msg = new ChatMessage(sender.DeviceID, sender.UserName, mt, messageText, recipientID)
            {
                MessageRelayTime = timestamp,
            };
            App.Current.Dispatcher.Invoke(() => DisplayGroupMessage(msg));

        }
        private void DisplayGroupMessage(UserInfo sender, string messageText, DateTime timestamp)
        {
            ServerInfoMessage msg = new ServerInfoMessage
            {
                Message = messageText,
                UserInfo = sender
            };
            App.Current.Dispatcher.Invoke(() => DisplayGroupMessage(msg));
        }
        private void DisplayGroupMessage(ChatMessage msg)
        {
            ViewModelGroupChat vm = (ViewModelGroupChat)VmHelper.Locator.ViewModelMainWindow.ChatViewModels.Union(this.ClosedConversations.Values).FirstOrDefault(x => x.ID == msg.RecipientID);

            if (vm == null)
            {
                var senderClient = VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID);
                var group = senderClient.ChatGroups.FirstOrDefault(x => x.ID == msg.RecipientID);

                vm = new ViewModelGroupChat(this, new JiveChatGroupModel(group.ID, group.Name), VmHelper.Locator.ViewModelContactList.CurrentUser, true, false);
                ClosedConversations[group.ID] = vm;
            }

            DisplayChatMessage(msg, vm);

        }
        private void DisplayChatMessage(ChatMessage msg, ViewModelChat vm)
        {
            var displayMessageOptions = DisplayMessageOptions.ChatMessageOption;
            if (msg.MessageType == MessageType.Nudge)
                displayMessageOptions = DisplayMessageOptions.NudgeOptions;
            else if (msg.MessageType == MessageType.Error)
                displayMessageOptions = DisplayMessageOptions.ErrorOptions;
            vm.DisplayMessage(new DisplayMessage(VmHelper.Locator.ViewModelContactList.Contacts.FirstOrDefault(x => x.UserID == msg.SenderID), msg.Message, displayMessageOptions, msg.MessageRelayTime.Value));
        }
        private void DisplayGroupMessage(ServerInfoMessage msg)
        {
            var chats = VmHelper.Locator.ViewModelMainWindow.ChatViewModels.Union(this.ClosedConversations.Values).Where(x => x.GetType() == typeof(ViewModelGroupChat) && msg.UserInfo.ChatGroups.Any(y => y.ID == x.ID));

            foreach (ViewModelGroupChat vm in chats)
            {
                UserInfo sender = CurrentUserInfo;
                ((ViewModelGroupChat)vm).DisplayMessage(new DisplayMessage(VmHelper.Locator.ViewModelContactList.CurrentUser, msg.Message, DisplayMessageOptions.ServerStatusMessageOption, DateTime.Now));
            }
        }

        public Dictionary<string, IEmoticon> GetEmoticons(string userID, bool includeDefault, bool includeCustom, bool includeLineAndSpacer)
        {
            Dictionary<string, IEmoticon> allEmoticons = new Dictionary<string, IEmoticon>();
            if (includeLineAndSpacer)
                allEmoticons.AddRange(this.SymbolEmoticons);

            if (CurrentUserInfo == null)
                return allEmoticons;

            if (includeCustom)
            {
                Messenger.Default.Send(new NotificationMessageAction<IContact>(this, userID, "GetContactFromUserID", val =>
                    {
                        if (val != null && val is JiveContactModel)
                        {
                            var user = (JiveContactModel)val;

                            if (user.CustomEmoticons != null)
                            {
                                foreach (JiveCustomEmoticonModel emoticon in user.CustomEmoticons)
                                {
                                    allEmoticons.Add(emoticon.EmoticonShortcut, emoticon);
                                }

                            }
                        }
                    }));
            }
            if (includeDefault)
            {
                foreach (JiveDefaultEmoticonModel model in DefaultEmoticons)
                {
                    foreach (string s in model.EmoticonShortcut)
                    {
                        string key = s.ToLower();
                        if (!allEmoticons.ContainsKey(key))
                            allEmoticons.Add(key, model);
                        if (!allEmoticons.ContainsKey(s))
                            allEmoticons.Add(s, model);
                    }
                }
            }
            return allEmoticons;
        }


        public bool ValidateEmoticonShortcut(string str, out string error)
        {
            return ValidateEmoticonShortcut(str, string.Empty, out error);
        }
        public bool ValidateEmoticonShortcut(string str, string exludeShortcut, out string error)
        {
            error = string.Empty;
            List<string> keys = GetEmoticons(CurrentUserInfo.UserID, true, true, true).Keys.ToList();
            if (!String.IsNullOrEmpty(exludeShortcut))
            {
                if (keys.Contains(exludeShortcut))
                    keys.Remove(exludeShortcut);
            }

            if (str.Length < 2)
            {
                error = "Minimum 2 characters";
                return false;
            }
            if (str.Length > 10)
            {
                error = "Max 10 characters";
                return false;
            }

            string conflictedKey = keys.FirstOrDefault(x => str.Contains(x) || x.Contains(str));
            if (!string.IsNullOrEmpty(conflictedKey))
            {
                error = "Shortcut already in use or conflicted: " + conflictedKey;
                return false;
            }
            return true;
        }
        public string GetSpotifyNowPlayingText()
        {
            string format = UserSettings.NowPlayingFormat;
            var status = SpotifyApi.Status;
            if (status.error != null)
                return "N/A";
            format = format.Replace("{artist}", status.track.artist_resource.name);
            format = format.Replace("{title}", status.track.track_resource.name);
            format = format.Replace("{album}", status.track.album_resource.name);
            format = format.Replace("{elapsed}", StaticMethods.TimeSpanToFormattedString((int)status.playing_position));
            format = format.Replace("{duration}", StaticMethods.TimeSpanToFormattedString((int)status.track.length));
            format = format.Replace("{status}", status.playing ? "Playing" : "Paused");

            return format;
        }
        public Tuple<string, TrackInfo> GetWinampNowPlayingText()
        {
            try
            {
                TrackInfo info = WinampApi.GetCurrentTrackInfo();
                string format = UserSettings.NowPlayingFormat;
                string songtitle = info.Title != null ? info.Title.ToString() : "N/A";
                string albumTitle = info.Album != null ? info.Album.ToString() : "N/A";
                string artistName = info.Artist != null ? info.Artist.ToString() : "N/A";
                int durationInSeconds = (int)info.TrackLength / 1000;
                int positionInSeconds = (int)info.TrackPosition / 1000;
                format = format.Replace("{artist}", artistName);
                format = format.Replace("{title}", songtitle);
                format = format.Replace("{album}", albumTitle);
                format = format.Replace("{elapsed}", StaticMethods.TimeSpanToFormattedString(durationInSeconds));
                format = format.Replace("{duration}", StaticMethods.TimeSpanToFormattedString(positionInSeconds));
                format = format.Replace("{status}", info.PlaybackStatus.ToString());


                return new Tuple<string, TrackInfo>(format, info);
            }
            catch (Exception ex)
            {
                return new Tuple<string, TrackInfo>("N/A", null);
            }
        }
        public void Cleanup()
        {
            ClearConnectedClients();
            ClosedConversations.Clear();
            FriendUsernames.Clear();
            //  EmoticonExistsCache.Clear();
            _receivedClientList = false;
            VmHelper.Locator.ViewModelNotifications.Cleanup();
            StopReceiptTimer();
            StopStatusTimer();

            if (!Properties.Settings.Default.RememberMyPassword)
                Properties.Settings.Default.Password = "";
            if (!Properties.Settings.Default.RememberMe)
                Properties.Settings.Default.Username = "";
        }
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}
